package top.lingkang.finalgateway.core;

import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.NettyRuntime;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import top.lingkang.finalgateway.admin.entity.SslEntity;
import top.lingkang.finalgateway.admin.mapper.SslMapper;
import top.lingkang.finalgateway.config.RouterConfig;
import top.lingkang.finalgateway.core.ssl.SslConfig;
import top.lingkang.finalgateway.core.ssl.SslJksServerInitHandler;
import top.lingkang.finalgateway.core.ssl.SslSelfSignedServerInitHandler;
import top.lingkang.finalgateway.core.ssl.SslServerInitHandler;
import top.lingkang.finalgateway.monitor.IpListCheck;
import top.lingkang.finalgateway.monitor.MonitorInduction;
import top.lingkang.finalgateway.utils.CommonUtils;
import top.lingkang.finalgateway.utils.SslUtils;

import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author lingkang
 * Created by 2024/5/5
 */
@Slf4j
public class NettyService {
    public final RouterConfig config;
    private EventLoopGroup bossGroup, workerGroup;
    private ChannelFuture future;
    public String error;

    public NettyService(RouterConfig config) {
        this.config = config;
    }

    public void start() {
        if (CommonUtils.portIsUse(config.getPort())) {
            log.error("服务启动失败，端口被占用：{}", config.getPort());
            // System.exit(0);
            error = "服务启动失败，端口被占用：" + config.getPort();
            return;
        }
        bossGroup = new NioEventLoopGroup(config.getDispatchThread());
        workerGroup = new NioEventLoopGroup(
                config.getThreadNumber() > 0 ? config.getThreadNumber() : Math.max(CommonUtils.isLinux() ? 64 : 16, SystemPropertyUtil.getInt(
                        "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)));
        try {
            // 初始化数据
            initData();

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 2048)
                    // 客户端关闭连接时，自动关闭channel
                    .childOption(ChannelOption.ALLOW_HALF_CLOSURE, true)
                    //置连接为保持活动的状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 禁用Nagle算法，即使用小数据包即时传输
                    .childOption(ChannelOption.TCP_NODELAY, true)
            // .handler(new LoggingHandler(LogLevel.INFO))
            // .childHandler(new ServerInitHandler(config))
            ;
            if (StrUtil.isNotBlank(config.getSsl())) {
                if ("0".equals(config.getSsl())) {// 使用临时自签证书
                    SslContext sslContext = null;
                    try {
                        // 使用临时签发的一个证书
                        SelfSignedCertificate ssc = new SelfSignedCertificate();
                        sslContext = SslContextBuilder.forServer(ssc.certificate(),
                                ssc.privateKey()).build();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    bootstrap.childHandler(new SslSelfSignedServerInitHandler(config, sslContext));
                    log.info("端口: {}, 使用临时自签SSL证书", config.getPort());
                } else { // 使用证书
                    bootstrap.childHandler(sslChannelHandler());
                }
            } else
                bootstrap.childHandler(new ServerInitHandler(config));

            future = bootstrap.bind(config.getPort());
        } catch (Exception e) {
            log.error("启动gateway服务失败, 端口: {}", config.getPort(), e);
            error = "启动gateway服务失败, 端口: " + config.getPort() + ", " + e.getMessage();
            return;
        }
        if (StrUtil.isBlank(config.getSsl()))
            log.info("启动 --> gateway: http://localhost:{}", config.getPort());
        else
            log.info("启动 --> gateway: https://localhost:{}", config.getPort());
    }

    private ChannelHandler sslChannelHandler() {
        SslMapper sslMapper = Solon.context().getBean(SslMapper.class);
        SslEntity ssl = sslMapper.selectById(config.getSsl());
        if ("openssl".equals(ssl.getType())) {
            try {
                SslContextBuilder forServer = SslContextBuilder.forServer(
                        new ByteArrayInputStream(ssl.getPublicKey()),
                        new ByteArrayInputStream(ssl.getPrivateKey()),
                        StrUtil.isNotBlank(ssl.getPassword()) ? ssl.getPassword() : null
                );
                SslConfig sslConfig = new SslConfig();
                sslConfig.setEnableProtocols(ONode.loadStr(ssl.getEnableProtocols()).toObjectList(String.class).toArray(new String[0]));
                sslConfig.setEnableCipherSuites(ONode.loadStr(ssl.getEnableCipherSuites()).toObjectList(String.class).toArray(new String[0]));
                if (ssl.getDomain() != null)
                    config.setSslAddress("https://" + ssl.getDomain() + ":" + config.getPort());
                return new SslServerInitHandler(config, sslConfig, forServer.build());
            } catch (Exception e) {
                error = "证书加载失败, " + e.getMessage();
                log.error("证书加载失败, {} - {}", config.getPort(), config.getSsl(), e);
                throw new RuntimeException("证书加载失败", e);
            } finally {
                outLog(ssl.getDomain());
            }
        } else {
            try {
                SSLContext sslContext = SslUtils.getSslContext(ssl.getPassword(), new ByteArrayInputStream(ssl.getPublicKey()));
                SslConfig sslConfig = new SslConfig();
                sslConfig.setEnableProtocols(ONode.loadStr(ssl.getEnableProtocols()).toObjectList(String.class).toArray(new String[0]));
                sslConfig.setEnableCipherSuites(ONode.loadStr(ssl.getEnableCipherSuites()).toObjectList(String.class).toArray(new String[0]));
                if (ssl.getDomain() != null)
                    config.setSslAddress("https://" + ssl.getDomain() + ":" + config.getPort());
                return new SslJksServerInitHandler(config, sslConfig, sslContext);
            } catch (Exception e) {
                error = "jks证书加载失败, " + e.getMessage();
                log.error("jks证书加载失败, {} - {}", config.getPort(), config.getSsl(), e);
                throw new RuntimeException("jks证书加载失败", e);
            } finally {
                outLog(ssl.getDomain());
            }
        }
    }

    private void outLog(String domain) {
        if (domain != null && domain.contains("="))
            domain = domain.substring(domain.indexOf("=") + 1);
        if (domain == null)
            domain = "127.0.0.1";
        log.info("端口: {}, 使用SSL证书: https://{}:{}", config.getPort(), domain, config.getPort());
    }

    private void initData() {
        config.setMonitor(new MonitorInduction(config.getPort()));
        config.setIpListCheck(new IpListCheck(config.getPort()));
        config.setClient(new OkHttpClient.Builder()
                .connectTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
                .readTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
                .callTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
                .build());
        config.setHttpDataFactory(new DefaultHttpDataFactory());
        config.setHandlerMap(new HashMap<>());
    }

    public void shutdown() {
        if (bossGroup == null)
            return;
        bossGroup.shutdownGracefully();
        if (workerGroup != null)
            workerGroup.shutdownGracefully();
        if (future != null) {
            future.channel().close();
        }
        log.info("服务停止. 端口：{}", config.getPort());
    }
}
